
/**
 * Copyright 2009 cestum.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
package org.waveprotocol.wave.model.document.operation.impl {
import org.goverla.collections.ArrayList;
import org.waveprotocol.wave.model.document.operation.AnnotationBoundaryMap;
import org.waveprotocol.wave.model.document.operation.impl.util.ArrBuilder;


/**
 * A simple, immutable implementation of {@link AnnotationBoundaryMap} that
 * does almost all necessary validation checks.
 */
// TODO: Validate key characters better.
// Should probably only allow whatever is allowed in an xml attribute, + "/"
// TODO: Keep these in sorted order?
public class AnnotationBoundaryMapImpl implements AnnotationBoundaryMap {

  //public static var EMPTY_MAP:AnnotationBoundaryMapImpl= builder().build();

  private var changeKeys:Array;
  private var changeOldValues:Array;
  private var changeNewValues:Array;
  private  var endKeys:Array;



  public static function builder():ArrBuilder {
    return new ArrBuilder();
  }

  public function AnnotationBoundaryMapImpl(endKeys:Array, changeKeys:Array,
	      changeOldValues:Array, changeNewValues:Array) {
	    if (changeKeys.length != changeOldValues.length ||
	        changeKeys.length != changeNewValues.length) {
	      throw new Error(
	          "Change keys, new values, and old values sizes don't match");
	    }
		//FIXME find a way to find the unique key
	    var changeKeySet:ArrayList= new ArrayList(changeKeys);
	    var endKeySet:ArrayList= new ArrayList(endKeys);
	    if (changeKeySet.length != changeKeys.length || endKeySet.length != endKeys.length) {
	      throw new Error("Keys must all be unique");
	    }

	    if (changeKeySet.contains(null) || endKeySet.contains(null)) {
	      throw new Error("Null keys are not permitted");
	    }

	    if (changeKeySet.contains("") || endKeySet.contains("")) {
	      throw new Error("Empty-string keys are not permitted");
	    }

		var size1:int = changeKeys.length;		
		for (var i1:int=0; i1<size1; i1++) {
			var changeKey:String = changeKeys[i1] as String;
	      validateAnnotationKey(changeKey);
	    }

		
		var size2:int = endKeys.length;		
	    for (var i2:int=0; i2<size2; i2++) {
			var endKey:String = endKeys[i2] as String;
	      validateAnnotationKey(endKey);
	      if (changeKeySet.contains(endKey)) {
	        throw new Error("Change keys and end keys must be disjoint sets");
	      }
	    }

	    this.changeKeys = copy(changeKeys);
	    this.changeOldValues = copy(changeOldValues);
	    this.changeNewValues = copy(changeNewValues);
	    this.endKeys = copy(endKeys);
	  }

  
//  public function AnnotationBoundaryMapImpl(array:Array, array2:Array,
//		array3:Array, array4:Array) {
//	  this((String[])array,(String[])array2,(String[])array3,(String[])array4);
//}

/**
   * Copies an array. GWT does not seem to support Arrays.copyOf()
   */
//  private String[] copy(input:Array) {
	private function copy(input:Array):Array {
    var ret:Array= new Array(input.length);
    for (var i:int= 0; i < input.length; i++) {
      ret[i] = input[i];
    }
    return ret;
  }

  public static function validateAnnotationKey(key:String):void {
    if (key.indexOf("?") != -1 || key.indexOf("@") != -1) {
      throw new Error(
          "Annotation keys must not contain the '?' or '@' characters");
    }
  }

  
  public function changeSize():int {
    return changeKeys.length;
  }

  
  public function endSize():int {
    return endKeys.length;
  }

  
  public function getChangeKey(changeIndex:int):String {
    return changeKeys[changeIndex];
  }

  
  public function getNewValue(changeIndex:int):String {
    return changeNewValues[changeIndex];
  }

  
  public function getOldValue(changeIndex:int):String {
    return changeOldValues[changeIndex];
  }

  
  public function getEndKey(endIndex:int):String {
    return endKeys[endIndex];
  }

  
  public function toString():String {
    return DocumentOperationUtil.toConciseString(this);
  }
}
}